﻿#region Copyright and license information
// Copyright 2012 Richard Schneider
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using System;
using System.Globalization;

namespace NodaTime
{
    /// <summary>
    ///   Represents the nth or any Day in a period for a <see cref="RecurrenceRule"/>. 
    /// </summary>
    /// <remarks>
    ///   Corresponds to the <i>weekdaynum</i> of RFC 5545.  The textual representation,
    ///   see <see cref="ToString"/> and <see cref="NthDay(string)"/>, has the form:
    ///   <code>
    ///   weekdaynum  = [[plus / minus] ordwk] weekday
    ///   plus        = "+"
    ///   minus       = "-"
    ///   ordwk       = 1*2DIGIT       ;1 to 53
    ///   weekday     = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA"
    ///   </code>
    ///   <note>
    ///     minus weekday is equivalent to "-1<i>weekday</i>".
    ///   </note>
    /// </remarks>
    /// <example>
    ///   <list type="table">
    ///     <item><term>RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13</term><description>Every Friday the 13th.</description></item>
    ///     <item><term>RRULE:FREQ=YEARLY;BYDAY=13FR</term><description>Every 13th Friday of the year.</description></item>
    ///     <item><term>RRULE:FREQ=MONTHLY;BYDAY=1SU,-1SU</term><description>The first and last Sunday of every month.</description></item>
    ///   </list>
    /// </example>
    /// <seealso cref="RecurrenceRule"/>
    /// <seealso cref="RecurrenceRule.ByDays"/>
    public class NthDay
    {
        /// <summary>
        ///   Creates a new instance of the <see cref="NthDay"/> class.
        /// </summary>
        public NthDay()
        {
        }

        /// <summary>
        ///   Creates a new instance of the <see cref="NthDay"/> class with the specified
        ///   <see cref="IsoDayOfWeek"/>.
        /// </summary>
        /// <param name="dayOfWeek">
        ///   One of the <see cref="IsoDayOfWeek"/> enumeration values.
        /// </param>
        public NthDay(IsoDayOfWeek dayOfWeek)
        {
            this.DayOfWeek = dayOfWeek;
        }

        /// <summary>
        ///   Creates a new instance of the <see cref="NthDay"/> class from the
        ///   specified <see cref="string"/>.
        /// </summary>
        /// <param name="text">
        ///   A <see cref="string"/> representing a <b>NthDay</b> if the form of a
        ///   <i>weekdaynum</i> as described by RFC 5545.
        /// </param>
        /// <remarks>
        ///   <para>
        ///   <paramref name="text"/> is of the <i>weekdaynum</i> form:
        ///   <code>
        ///   weekdaynum  = [[plus / minus] ordwk] weekday
        ///   plus        = "+"
        ///   minus       = "-"
        ///   ordwk       = 1*2DIGIT       ;1 to 53
        ///   weekday     = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA"
        ///   </code>
        ///   </para>
        /// </remarks>
        public NthDay(string text)
        {
            if (string.IsNullOrEmpty(text) || text.Length < 2)
                throw new FormatException("The NthDay is invalid.");
            var day = text;
            if (text.Length > 2)
            {
                day = text.Substring(text.Length - 2);
                var number = text.Substring(0, text.Length - 2);
                try
                {
                    this.Occurrence = number == "-"
                      ? -1
                      : Int32.Parse(number, CultureInfo.InvariantCulture);
                }
                catch (FormatException e)
                {
                    throw new FormatException(string.Format("The NthDay is invalid. Expected an occurance number not '{0}'.", number), e);
                }
            }

            var dayIndex = Array.IndexOf(RecurrenceRule.Rfc5545WeekDayNames, day.ToUpperInvariant());
            if (dayIndex < 0)
                throw new FormatException(string.Format("The NthDay is invalid. Expected a day of week abbreviation not '{0}'.", day));
            this.DayOfWeek = (IsoDayOfWeek) (dayIndex + 1);
        }

        /// <summary>
        ///   The occurrence of the day within the period. 
        /// </summary>
        public int Occurrence;

        /// <summary>
        ///   The <see cref="IsoDayOfWeek"/> within the period.
        /// </summary>
        public IsoDayOfWeek DayOfWeek;

        /// <summary>
        ///   The RFC 5545 representation of the <see cref="NthDay"/>.
        /// </summary>
        /// <remarks>
        ///   The returned value is culturally invariant and can be used for "round tripping".
        /// </remarks>
        public override string ToString()
        {
            if (Occurrence == 0)
                return RecurrenceRule.Rfc5545WeekDayNames[(int) DayOfWeek - 1];

            return Occurrence.ToString(CultureInfo.InvariantCulture) + RecurrenceRule.Rfc5545WeekDayNames[(int) DayOfWeek- 1];
        }
    }

}
